package com.merry.api.validator;

import com.merry.api.constant.ApiConstant;
import com.merry.api.mapper.ApiMapper;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;

/**
 * API接口限流验证器
 */
@Service
@Order(3)
public class ApiLimitValidator implements ApiValidator {

    private static final Logger LOGGER = LoggerFactory.getLogger(ApiLimitValidator.class);

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private RedissonClient redisson;

    @Resource
    private ApiMapper mapper;

    /**
     * 限流验证，单位时间内调用次数不能大于配置的最大值
     */
    @Override
    public boolean verify(HttpServletRequest request) throws Exception {
        Long curr = System.currentTimeMillis();
        String paasId = request.getHeader(ApiConstant.PASSID);
        String url = request.getServletPath();
        String limitConfig = (String) redisTemplate.boundHashOps(ApiConstant.REDIS_KEY_API_CONFIG).get(paasId +
                ApiConstant.REDIS_KEY_SEPARATOR + url);
        if (StringUtils.isEmpty(limitConfig)) {
            limitConfig = mapper.queryApiLimitConfig(paasId, url);
            if (StringUtils.isNotEmpty(limitConfig)) {
                redisTemplate.boundHashOps(ApiConstant.REDIS_KEY_API_CONFIG).put(paasId +
                        ApiConstant.REDIS_KEY_SEPARATOR + url, limitConfig);
            }
        }
        int maxIntervalMinutes = -1;
        int maxCallTimes = -1;
        if (StringUtils.isNotEmpty(limitConfig)) {
            maxIntervalMinutes = Integer.parseInt(limitConfig.split(ApiConstant.REDIS_KEY_SEPARATOR)[0]);
            maxCallTimes = Integer.parseInt(limitConfig.split(ApiConstant.REDIS_KEY_SEPARATOR)[1]);
        }
        if (maxIntervalMinutes > 0 && maxCallTimes > 0) {
            // 获取锁
            RLock lock = redisson.getLock(ApiConstant.REDIS_KEY_PREFIX_LOCK + paasId +
                    ApiConstant.REDIS_KEY_SEPARATOR + url);
            try {
                // 最多等待时间，自动解锁时间
                boolean res = lock.tryLock(ApiConstant.MAX_REDIS_LOCK_WAIT_SECOND,
                        ApiConstant.MAX_REDIS_LOCK_UNLOCK_SECOND, TimeUnit.SECONDS);
                process(res, paasId, url, curr, maxCallTimes, maxIntervalMinutes);
                lock.unlock();
            } catch (InterruptedException ie) {
                LOGGER.error(ie.getLocalizedMessage(), ie);
                Thread.currentThread().interrupt();
            } catch (Exception e) {
                LOGGER.error(e.getLocalizedMessage(), e);
                throw new Exception("系统异常！");
            } finally {
                //最终解锁。
                if (lock.isLocked()) {
                    lock.forceUnlock();
                }
            }
        }
        return true;
    }

    private void process(boolean res, String paasId, String url, Long curr, int maxCallTimes, int maxIntervalMinutes) throws Exception{
        if (res) {
            Long len = redisTemplate.boundListOps(ApiConstant.REDIS_KEY_PREFIX_LIMIT + paasId +
                    ApiConstant.REDIS_KEY_SEPARATOR + url).size();
            len = len == null ? 0 : len;
            if (len < maxCallTimes) {
                redisTemplate.boundListOps(ApiConstant.REDIS_KEY_PREFIX_LIMIT + paasId +
                        ApiConstant.REDIS_KEY_SEPARATOR + url).rightPush(curr);
            }
            if (len >= maxCallTimes) {
                Long lastTime = (Long) redisTemplate.boundListOps(ApiConstant.REDIS_KEY_PREFIX_LIMIT + paasId +
                        ApiConstant.REDIS_KEY_SEPARATOR + url).index(0);
                lastTime = lastTime == null ? 0 : lastTime;
                if ((curr - lastTime) < maxIntervalMinutes * ApiConstant.TIME_MINUTES_SECOND *
                        ApiConstant.TIME_SECOND_MS) {
                    throw new Exception("规定时间内超过了调用限制");
                } else {
                    redisTemplate.boundListOps(ApiConstant.REDIS_KEY_PREFIX_LIMIT + paasId +
                            ApiConstant.REDIS_KEY_SEPARATOR + url).leftPop();
                    redisTemplate.boundListOps(ApiConstant.REDIS_KEY_PREFIX_LIMIT + paasId +
                            ApiConstant.REDIS_KEY_SEPARATOR + url).rightPush(curr);
                }
            }
        }
    }
}
